package cate.game.role.bag.prop;


import cate.common.table.d.*;
import cate.common.table.item.ItemBaseRow;
import cate.common.table.item.ItemCheatRow;
import cate.common.table.item.ItemPackRow;
import cate.common.table.item.PropPartRow;
import cate.common.table.res.drop.row.HangBagRow;
import cate.common.util.GameResult;
import cate.game.res.MixResAdder;
import cate.game.role.bag.furniture.Furniture;
import cate.game.role.bag.prop.continuepack.ContinuePacks;
import cp.solution.business.game.component.NoticeParam;
import cate.game.client.msg.MergeMsgSender;
import cate.game.mail.po.MailBuilder;
import cate.game.res.MixRes;
import cate.game.res.MixResItem;
import cate.game.role.Role;
import cate.game.role.bag.BagContainer;
import cate.game.role.bag.item.msg.ItemAddResp;
import cate.game.role.bag.prop.msg.PackUseTimesDataResp;
import cate.game.role.bag.prop.msg.PropAddResp;
import cate.game.role.bag.vo.TidNumVO;
import cate.game.role.mail.Mail;
import com.fasterxml.jackson.annotation.JsonIgnore;
import easy.java.dev.note.NoteClass;
import easy.java.dev.note.NoteField;
import easy.java.practice.errorcode.EcResult;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

@NoteClass(value = "")
public class PropBag extends BagContainer<Prop> {
    @JsonIgnore
    private long checkTime;

    @NoteField("map<礼包id，使用次数>")
    @JsonIgnore
    protected Map<Integer, Integer> packUseTimes;

    @NoteField("连开礼包")
    @JsonIgnore
    public ContinuePacks continuePacks;

    public PropBag() {

    }

    public PropBag(Role role){
        super(role);
    }

    @Override
    protected byte getItemType() {
        return GDObj.Type.PROP;
    }

    @Override
    public void initialize(Role role){
        super.initialize(role);
        if(packUseTimes == null){
            packUseTimes = new HashMap<>();
        }
        if(continuePacks == null){
            continuePacks = new ContinuePacks();
        }
        continuePacks.initialize(role);
    }

    @JsonIgnore
    public Map<Integer, Integer> getPackUseTimes(){
        return this.packUseTimes;
    }

    @Override
    public int calcCapacity() {
        return GDBag.Capcity.PROP;
    }

    @Override
    public ItemBaseRow getItemTpl(int templateId) {
        return role.getGame().table.item.base.get(templateId);
    }

    @Override
    protected ItemAddResp createItemAddMsg() {
        return new PropAddResp();
    }

    @Override
    protected Prop createItem(int tid, long num) {
        return new Prop(role, tid, num);
    }

    public void tick() {
        if (checkTime == 0 || System.currentTimeMillis() < checkTime) {
            return;
        }
        checkExpired();
        resetCheckTime();
    }

    private void checkExpired() {
        List<Prop> removeProps = new ArrayList<>();
        for (Prop prop : getList()) {
            if (prop.expired()) {
                removeProps.add(prop);
            }
        }
        if (!removeProps.isEmpty()) {
            for (Prop prop : removeProps) {
                ItemBaseRow row = prop.getTpl(role.getGame());
                List<NoticeParam> params = new ArrayList<>();
                params.add(new NoticeParam(GDProp.Colour.getColourByQuality(row.quality)));
                params.add(new NoticeParam(NoticeParam.NoticeType.TRANSLATE_STRING, row.name));
                Mail mail = new MailBuilder()
                        .setContentId(GDMail.Tid.ITEM_EXPIRED)
                        .setNoticeParamList(params)
                        .build();
                role.getMail().add(mail);
                removeItem(prop.uid, null);
            }
        }
    }

    /** 重新计算下次需要检测的时间 */
    private void resetCheckTime() {
        checkTime = 0;
        for (Prop item : getList()) {
            if(item.getExpireTime() > 0 && (checkTime == 0 || checkTime > item.getExpireTime())){
                checkTime = item.getExpireTime();
            }
        }
    }

    @Override
    protected void onItemCreated(Prop e){
        super.onItemCreated(e);
        if(e.getTpl(role.getGame()).type == GDProp.Type.CONTINUE_PACK){
            continuePacks.addPack(e);
        } else if (e.getTpl(role.getGame()).type == GDProp.Type.LUBI_ITEM) {
            role.getRes().lubiItem.addLubiItem(e);
        }
    }

    @Override
    protected void onItemIncreased(int tid, long num){
        super.onItemIncreased(tid,num);
        ItemBaseRow itemBaseRow = getItemTpl(tid);
        if (itemBaseRow != null && itemBaseRow.willExpired()) {
            resetCheckTime();
        }
    }

    /**
     * 出售道具
     */
    public void sellProps(List<TidNumVO> items){
        if(CollectionUtils.isEmpty(items)){
            role.getGame().notice.message(role, "出售道具有误");
            return;
        }
        MergeMsgSender sender = new MergeMsgSender();
        MixRes reward = new MixRes();
        for(TidNumVO vo : items){
            if (vo.num <= 0) {
                role.getGame().notice.message(role, "出售数量有误");
                continue;
            }
            if (count(vo.tid) < vo.num) {
                role.getGame().notice.message(role, "道具数量不足");
                continue;
            }
            ItemBaseRow baseTpl = getItemTpl(vo.tid);
            MixRes sellRes = null;
            if (baseTpl != null && !StringUtils.isBlank(baseTpl.sellResCode) && !StringUtils.equals("0", baseTpl.sellResCode)) {
                sellRes = new MixRes(baseTpl.sellResCode);
            }
            if (sellRes == null) {
                role.getGame().notice.message(role, "该道具不支持出售");
                continue;
            }
            EcResult<Void> r = sellRes.multiply(vo.num).add(role, sender);
            if (!r.ok()) {
                role.getGame().notice.message(role, r, true);
                continue;
            }
            removeItem(vo.tid, vo.num, sender);
            reward.addList(sellRes);
        }
        sender.send(role, true);
        role.getGame().notice.dialogReward(role, reward);
    }

    /**
     * 根据模板id使用道具
     * @param tid 模板id
     * @param num 数量
     */
    public void usePropByTid(int tid, int num){
        ItemBaseRow tpl = role.getGame().table.item.base.get(tid);
        if (tpl == null) {
            log.warn("尝试使用不存在的道具 id={}", tid);
            return;
        }
        if (num <= 0) {
            role.getGame().notice.message(role, "道具不足");
            return;
        }

        long availableNum = count(tid);
        if (availableNum < num) {
            role.getGame().notice.message(role, "道具不足");
            return;
        }

        EcResult<Void> r = usePropCheck(tpl);
        if (!r.ok()) {
            role.getGame().notice.message(role, r);
            return;
        }

        switch (tpl.type) {
            case GDProp.Type.PACK_FIX:
            case GDProp.Type.PACK_RANDOM:
            case GDProp.Type.PACK_COST_ITEM:
                useGiftPack(tid, num, tpl.name);
                break;
            case GDProp.Type.HANG_BAG:
                useHangBag(tid, num);
                break;
            case GDProp.Type.DECORATION_ACTIVE_ITEM:
                role.getShow().useItem(tid, num);
                break;
            case GDProp.Type.PROP_PART:
                propCompose(tid, num);
                break;
            default:
                break;
        }
    }

    private void propCompose(int tid, int num) {
        PropPartRow row = role.getGame().table.item.propPart.get(tid);
        if (row == null) {
            role.getGame().notice.message(role, "该道具无法合成");
            return;
        }
        if (row.num < 1) {
            role.getGame().notice.message(role, "该道具无法合成");
            return;
        }
        if (num % row.num != 0) {
            role.getGame().notice.message(role, "合成数量有误");
            return;
        }
        int scale = num / row.num;
        MixRes consume = new MixRes(GDObj.getMixResStr(GDObj.Type.PROP, tid, num));
        GameResult<MixResItem> consumeRes = consume.consume(role);
        if (!consumeRes.ok()) {
            role.getGame().notice.tipResItem(role, consumeRes.data);
            role.getGame().notice.message(role, consumeRes);
            return;
        }
        new MixResAdder()
                .setSendMailWhenFull(true)
                .setDialogReward(true)
                .setRes(new MixRes(GDObj.getMixResStr(GDObj.Type.PROP, row.rewardId, scale)))
                .exe(role);
    }

    /**
     * 使用道具校验
     * @param row ItemBaseRow
     * @return EcResult
     */
    private GameResult<Void> usePropCheck(ItemBaseRow row) {
        GameResult<Void> r = new GameResult<>();
        if (row == null) {
            return r.fail("道具配置不存在");
        }
        int openDay = role.getGame().status.getOpenDay();
        if (row.useItemOpenDayMin > 0 && openDay < row.useItemOpenDayMin) {
            return r.fail("未到开放时间");
        }
        if (row.useItemOpenDayMax > 0 && openDay > row.useItemOpenDayMax) {
            return r.fail("未到开放时间");
        }
        return r.success();
    }

    /**
     * 使用礼包
     * @param tid 模板id
     * @param num 数量
     */
    private void useGiftPack(int tid, int num, String packName) {
        ItemPackRow packTpl = role.getGame().table.item.pack.get(tid);
        if (packTpl == null) {
            log.warn("缺少礼包配置与该道具对应 id={}", tid);
            return;
        }

        // 校验消耗道具
        MixRes consume = null;
        if (packTpl.consumeStr != null) {
            consume = new MixRes(packTpl.consumeStr).multiply(num);
            EcResult<MixResItem> r = consume.consumeCheck(role, null);
            if (!r.ok()) {
                role.getGame().notice.tipResItem(role, r.data);
                return;
            }
        }

        MixRes rl = new MixRes();
        MixRes announceReward = new MixRes();
        MixRes announceItems = null;
        //礼包开启次数
        int cheatTimes = cheatTimes(packTpl.id);
        for (int i = 0; i < num; i++) {
            MixRes reward = randomPackCheat(packTpl,  ++ cheatTimes);
            if(reward != null){
                rl.addList(reward);
            }else{
                reward = buildPack(packTpl.output);
                rl.addList(reward);
            }
            if(packTpl.announce){
                if(announceItems == null){
                    announceItems = new MixRes(packTpl.announceItemsStr);
                }
                for(MixResItem item : reward.items){
                    for (MixResItem announce : announceItems.items) {
                        if(item.type == announce.type && item.tid() == announce.tid() && item.num == announce.num){
                            announceReward.items.add(announce);
                        }
                    }
                }
            }
            //如果这次是大奖，不管是保底还是运气好
            if(bigPrize(packTpl, reward)){
                cheatTimes = 0;
            }
        }
        // 加东西校验
        EcResult<Void> addResult = rl.addCheck(role);
        if (!addResult.ok()) {
            role.getGame().notice.message(role, addResult.message);
            return;
        }
        // 扣东西
        if (consume != null) {
            consume.consumeDirect(role, null);
        }
        // 加东西
        addResult = rl.add(role);
        if (!addResult.ok()) {
            role.getGame().notice.message(role, addResult.message);
        } else {
            updateCheatTimes(packTpl, cheatTimes);
            MergeMsgSender sender = new MergeMsgSender();
            role.getBag().prop.removeItem(tid, num, sender);
            sender.send(role, true);
            role.getGame().notice.dialogReward(role, rl);
        }
    }

    private void updateCheatTimes(ItemPackRow packTpl, int times){
        if(!isCheatPack(packTpl)){
            return;
        }
        packUseTimes.put(packTpl.id, times);
        role.sendNow(new PackUseTimesDataResp(packUseTimes));
    }

    private int cheatTimes(int packId){
        return packUseTimes.getOrDefault(packId, 0);
    }

    private boolean bigPrize(ItemPackRow packTpl, MixRes reward){
        ItemCheatRow cheatRow = role.getGame().table.item.cheat.get(packTpl.id);
        if(cheatRow == null){
            return false;
        }
        return cheatRow.bigPrize.equals(reward.toItemsStr());
    }

    private boolean isCheatPack(ItemPackRow packTpl){
        return role.getGame().table.item.cheat.get(packTpl.id) != null;
    }

    private MixRes randomPackCheat(ItemPackRow packRow, int times){
        ItemCheatRow cheatRow = role.getGame().table.item.cheat.get(packRow.id);
        if(cheatRow == null){
            return null;
        }
        if(times >= cheatRow.cheatTimes){
            return new MixRes(cheatRow.bigPrize);
        }
        return null;
    }

    /**
     * 构建礼包奖励
     * @param packOutput pack
     * @return 奖励
     */
    private MixRes buildPack(ItemPackRow.PackOutput packOutput){
        MixRes list = new MixRes();
        if (GDProp.Type.PACK_FIX == packOutput.type) {
            for (ItemPackRow.FixItem e : packOutput.fixItemList) {
                MixResItem item = new MixResItem();
                item.type = e.type;
                item.tid(e.tid);
                item.num = e.num;
                list.addItem(item);
            }
        } else if (GDProp.Type.PACK_RANDOM == packOutput.type) {
            double random = Math.random();
            for (ItemPackRow.RandomItem e : packOutput.randomItemList) {
                if (random <= e.weight) {
                    if (e.type == GDObj.Type.FURNITURE) {
                        Furniture furniture = new Furniture(role, e.tid);
                        furniture.initialize(role);
                        list.items.add(new MixResItem(furniture));
                        break;
                    } else {
                        MixResItem item = new MixResItem();
                        item.type = e.type;
                        item.tid(e.tid);
                        item.num = e.num;
                        list.addItem(item);
                        break;
                    }
                }
            }
        } else if (GDProp.Type.PACK_COST_ITEM == packOutput.type) {
            double random = Math.random();
            for (ItemPackRow.RandomItem e : packOutput.costItemList) {
                if (random <= e.weight) {
                    MixResItem item = new MixResItem();
                    item.type = e.type;
                    item.tid(e.tid);
                    item.num = e.num;
                    list.addItem(item);
                    break;
                }
            }
        } else if (GDProp.Type.PACK_UNCERTAIN_NUMS == packOutput.type) {
            list = new MixRes(packOutput.uncertainItemList.get().res);
        }
        return list;
    }

    private void useHangBag(int tid, int num) {
        HangBagRow itemRow = role.getGame().table.drop.hangBag.get(tid);
        if (itemRow == null) {
            role.getGame().notice.message(role, "缺少挂机资源袋配置");
            return;
        }
        long totalHangMinute = num * itemRow.hangMinute;
        MixRes reward = new MixRes();
        switch (itemRow.type) {
            case GDProp.HangType.HERO_EXP:
                long value = role.getFarm().adventure.calcHangValue(GDObj.Type.NUM, GDObj.Num.HERO_EXP, totalHangMinute);
                if (value > 0) {
                    reward.addItem(new MixResItem(GDObj.Type.NUM, GDObj.Num.HERO_EXP, value));
                }
                break;
            case GDProp.HangType.GOLD:
                value = role.getFarm().adventure.calcHangValue(GDObj.Type.NUM, GDObj.Num.M0, totalHangMinute);
                if (value > 0) {
                    reward.addItem(new MixResItem(GDObj.Type.NUM, GDObj.Num.M0, value));
                }
                break;
            case GDProp.HangType.STONE:
                value =  role.getFarm().adventure.calcHangValue(GDObj.Type.PROP, GDProp.TID.GRADE_STONE, totalHangMinute / 60);
                if (value > 0) {
                    reward.addItem(new MixResItem(GDObj.Type.PROP, GDProp.TID.GRADE_STONE, value));
                }
                break;
            case GDProp.HangType.ALL:
                value = role.getFarm().adventure.calcHangValue(GDObj.Type.NUM, GDObj.Num.HERO_EXP, totalHangMinute);
                if (value > 0) {
                    reward.addItem(new MixResItem(GDObj.Type.NUM, GDObj.Num.HERO_EXP, value));
                }
                value = role.getFarm().adventure.calcHangValue(GDObj.Type.NUM, GDObj.Num.M0, totalHangMinute);
                if (value > 0) {
                    reward.addItem(new MixResItem(GDObj.Type.NUM, GDObj.Num.M0, value));
                }
                value =  role.getFarm().adventure.calcHangValue(GDObj.Type.PROP, GDProp.TID.GRADE_STONE, totalHangMinute / 60);
                if (value > 0) {
                    reward.addItem(new MixResItem(GDObj.Type.PROP, GDProp.TID.GRADE_STONE, value));
                }
                break;
            default:
                role.getGame().notice.message(role, "缺少挂机资源袋配置");
                return;
        }
        reward.add(role);
        role.getBag().prop.removeItem(tid, num, null);
        role.getGame().notice.dialogReward(role, reward);
    }

    /**
     * 根据唯一id使用道具
     * @param uid 唯一id
     */
    public void usePropByUid(String uid) {
        Prop item = getItem(uid);
        if (item == null) {
            log.warn("尝试使用不存在的道具 uid={}", uid);
            return;
        }
        if (item.num <= 0) {
            role.getGame().notice.message(role, "道具不足");
            return;
        }
        ItemBaseRow row =  item.getTpl(role.getGame());
        GameResult<Void> r = usePropCheck(row);
        if (!r.ok()) {
            role.getGame().notice.message(role, r);
            return;
        }
        switch (row.type) {
            case GDProp.Type.DECORATION_ACTIVE_ITEM:
                role.getShow().useItem(item);
                break;
            default:
                log.warn("尝试使用不存在的道具类型 type={}", row.type);
                break;
        }
    }

    public void usePack(List<Integer> indexes, int tid, int num){
        ItemBaseRow tpl = role.getGame().table.item.base.get(tid);
        if (tpl == null) {
            log.warn("尝试使用不存在的道具 id={}", tid);
            return;
        }
        GameResult<Void> r = usePropCheck(tpl);
        if (!r.ok()) {
            role.getGame().notice.message(role, r);
            return;
        }

        ItemPackRow packTpl = role.getGame().table.item.pack.get(tid);
        if (packTpl == null) {
            log.warn("缺少礼包配置与该道具对应 id={}", tid);
            return;
        }
        if (packTpl.kind != indexes.size()) {
            role.getGame().notice.message(role, String.format("请选择其中的%s项目", packTpl.kind));
            return;
        }

        long availableNum = count(tid);
        if (availableNum < num) {
            role.getGame().notice.message(role, "礼包数量不够");
            return;
        }
        MixRes rl = new MixRes();
        MixRes tplRL = choose(packTpl, indexes);
        for (int i = 0; i < num; i++) {
            rl.addList(tplRL);
        }
        EcResult<Void> addResult = rl.add(role);
        if (!addResult.ok()) {
            role.getGame().notice.message(role, addResult.message);
        } else {
            MergeMsgSender sender = new MergeMsgSender();
            removeItem(tid, num, sender);
            sender.send(role, true);
            role.getGame().notice.dialogReward(role, rl);
        }
    }

    /**
     * 自选礼包
     * @param packRow pack row
     * @param indexes 选择id
     * @return 奖励
     */
    private MixRes choose(ItemPackRow packRow, List<Integer> indexes) {
        MixRes reward = new MixRes();
        if (indexes.size() > packRow.kind) {
            log.warn("试图获取比策划配置更多的种类的物品，这是一种非法的攻击行为 indexes={}, kind={}", indexes, packRow.kind);
            indexes = new ArrayList<>();
            indexes.add(0);
        }
        for (int i = 0; i < packRow.output.chooseItemList.size(); i++) {
            if (indexes.contains(i)) {
                final ItemPackRow.ChooseItem e = packRow.output.chooseItemList.get(i);
                MixResItem item = new MixResItem();
                item.type = e.type;
                item.tid(e.tid);
                item.num = e.num;
                reward.addItem(item);
            }
        }
        return reward;
    }

    public EcResult<Void> useContinuePack(String uid, int selectedIndex){
        return continuePacks.usePack(uid, selectedIndex);
    }
}
